﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Linq.Expressions;

namespace SharePoint.Contrib
{

    /// <summary>
    /// Utility class responsible for translating expression trees into CAML
    /// </summary>
    public class CamlExpressionTree
    {
        private XElement filter = null;

        /// <summary>
        /// The Filter to use (Where, OrderBy, GroupBy)
        /// </summary>
        public string Filter
        {
            set
            {
                filter = new XElement(value);
            }
        }

        public CamlExpressionTree()
        {
        }

        /// <summary>
        /// Static constructor initializer.
        /// </summary>
        public static CamlExpressionTree WithFilter()
        {
            CamlExpressionTree instance = new CamlExpressionTree();
            return instance;
        }

        /// <summary>
        /// Setup the translator with the Where filter
        /// </summary>
        public CamlExpressionTree Where()
        {
            this.Filter = "Where";
            return this;
        }

        /// <summary>
        /// Translate into CAML
        /// </summary>
        public virtual string Translate()
        {
            return this.ToString();
        }

        public override string ToString()
        {
            return filter.ToString();
        }

        /// <summary>
        /// Translate into CAML
        /// </summary>
        public virtual string Translate(Expression expression)
        {
            filter.Add(Visit(expression));
            return filter.ToString(SaveOptions.DisableFormatting);
        }

        protected virtual XElement Visit(Expression expression)
        {
            if (expression == null)
            {
                return null;
            }

            switch (expression.NodeType)
            {
                case ExpressionType.Call:
                    return VisitMethodCall(expression as MethodCallExpression);
                case ExpressionType.MemberAccess:
                    return VisitMemberAccess(expression as MemberExpression);
                case ExpressionType.Constant:
                    return VisitConstant(expression as ConstantExpression);
                case ExpressionType.Convert:
                    return VisitUnary(expression as UnaryExpression);
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                case ExpressionType.LessThan:
                case ExpressionType.LessThanOrEqual:
                case ExpressionType.GreaterThan:
                case ExpressionType.GreaterThanOrEqual:
                case ExpressionType.Equal:
                case ExpressionType.NotEqual:
                    return VisitBinary(expression as BinaryExpression);
                default:
                    return VisitUnknown(expression);
            }
        }

        protected virtual XElement VisitUnknown(Expression expression)
        {
            throw new Exception(string.Format("Unhandled expression type: '{0}'", expression.NodeType));
        }

        protected virtual XElement VisitBinary(BinaryExpression binary)
        {
            XElement node = ParseNodeType(binary.NodeType);

            XElement left = Visit(binary.Left);
            XElement right = Visit(binary.Right);

            if (left != null && right != null)
            {
                node.Add(left, right);
            }

            return node;
        }

        protected virtual XElement ParseNodeType(ExpressionType type)
        {
            XElement node;

            switch (type)
            {
                case ExpressionType.AndAlso:
                case ExpressionType.And:
                    node = new XElement("And");
                    break;
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                    node = new XElement("Or");
                    break;
                case ExpressionType.Equal:
                    node = new XElement("Eq");
                    break;
                case ExpressionType.GreaterThan:
                    node = new XElement("Gt");
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    node = new XElement("Geq");
                    break;
                case ExpressionType.LessThan:
                    node = new XElement("Lt");
                    break;
                case ExpressionType.LessThanOrEqual:
                    node = new XElement("Leq");
                    break;
                default:
                    throw new Exception(string.Format("Unhandled expression type: '{0}'", type));
            }

            return node;
        }

        protected virtual XElement VisitMethodCall(MethodCallExpression methodcall)
        {
            XElement node;
            XElement left = Visit(methodcall.Object);
            XElement right = Visit(methodcall.Arguments[0]);

            switch (methodcall.Method.Name)
            {
                case "Contains":
                    node = new XElement("Contains");
                    break;
                case "StartsWith":
                    node = new XElement("BeginsWith");
                    break;
                default:
                    throw new Exception(string.Format("Unhandled method call: '{0}'", methodcall.Method.Name));
            }

            if (left != null && right != null)
            {
                node.Add(left, right);
            }

            return node;

        }

        protected virtual XElement VisitMemberAccess(MemberExpression member)
        {
            if (member.Expression != null && member.Expression.NodeType == ExpressionType.Constant)
            {
                LambdaExpression lambda = Expression.Lambda(member);
                Delegate fn = lambda.Compile();
                return VisitConstant(Expression.Constant(fn.DynamicInvoke(null), member.Type));
            }
            else
            {
                if (member.Member.DeclaringType == typeof(DateTime) || member.Member.DeclaringType == typeof(Nullable<DateTime>))
                {
                    switch (member.Member.Name)
                    {
                        case "Now": // DateTime.Now doesn´t work in CAML, use it like Today 
                        case "Today":
                            return new XElement("Value", new XAttribute("Type", "DateTime"), new XElement("Today"));      
                        default:
                            LambdaExpression lambda = Expression.Lambda(member);
                            Delegate fn = lambda.Compile();
                            return VisitConstant(Expression.Constant(fn.DynamicInvoke(null), member.Type));
                    }
                }

                if (IsNullable(member.Type))
                {
                    return new XElement("FieldRef", new XAttribute("Nullable", "TRUE"), ParseMemberName(member.Member.Name));
                }

                return new XElement("FieldRef", ParseMemberName(member.Member.Name));
            }
        }

        protected virtual XElement VisitConstant(ConstantExpression constant)
        {
            return new XElement("Value", ParseValueType(constant.Type), constant.Value);
        }

        protected virtual XElement VisitUnary(UnaryExpression unary)
        {
            return Visit(unary.Operand);
        }

        protected virtual XAttribute ParseMemberName(string memberName)
        {
            return new XAttribute("Name", memberName);
        }

        protected virtual XAttribute ParseValueType(Type type)
        {
            string name = "Text";

            switch (type.Name)
            {
                case "DateTime":
                    name = "DateTime";
                    break;
                case "String":
                    name = "Text";
                    break;
                default:
                    throw new Exception(string.Format("Unhandled value type parser for: '{0}'", type.Name));
            }

            return new XAttribute("Type", name);
        }

        protected virtual bool IsNullable(Type type)
        {
            return type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
        }


    }
}
